package com.levi.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * @author shentong
 * @since 2022/3/9 9:40 PM Aop的5种通知方式
 */
@Aspect
@Component
@Slf4j
public class WebAspect {

	/**
	 * 0.切入点 1.前置通知 2.后置返回通知 3.后置异常通知 4。最终通知 5。环绕通知
	 */
	/**
	 * 切入点
	 */
	@Pointcut("execution(* com.levi.controller.TestAopController.*(..))")
	public void potinCut() {
		log.info("开始切入>>>>>");
	}

	/**
	 * 前置通知
	 * @param joinPoint
	 */
	@Before("potinCut()")
	public void beforeAdvice(JoinPoint joinPoint) {
		log.info("开始前置通知>>>>>>");
		// 获取代理的目标方法的签名
		Signature signature = joinPoint.getSignature();
		log.info("方法签名是:>>" + signature.toString());
		// 返回代理的是哪一个方法
		String signatureName = signature.getName();
		log.info("代理的方法是:>>" + signatureName);
		Object[] args = joinPoint.getArgs();
		List<Object> argsList = Arrays.asList(args);
		log.info("目标方法的参数信息是:>>" + argsList);
	}

	/**
	 * 最终通知
	 */
	@After("potinCut()")
	public void afterAdvice() {
		log.info("最终通知>>>>");
	}

	/**
	 * 后置返回通知 如果参数中的第一个参数为JoinPoint，则第二个参数为返回值的信息
	 * 如果参数中的第一个参数不为JoinPoint，则第一个参数为returning中对应的参数 returning
	 * 只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知，否则不执行
	 * @param joinPoint
	 * @param keys
	 */
	@AfterReturning(value = "execution(* com.levi.controller.TestAopController.*(..))", returning = "keys")
	public void afterReturningAdvice(JoinPoint joinPoint, String keys) {
		System.out.println("----------- 后置返回通知 -----------");
		System.out.println("后置返回通知的返回值：" + keys);
	}

	/**
	 * 后置异常通知 定义一个名字，该名字用于匹配通知实现方法的一个参数名，当目标方法抛出异常返回后，将把目标方法抛出的异常传给通知方法； throwing
	 * 只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知，否则不执行，
	 * @param joinPoint
	 * @param e
	 */
	@AfterThrowing(value = "potinCut()", throwing = "e")
	public void afterThrowing(JoinPoint joinPoint, NullPointerException e) {
		log.info("后置异常通知:>>" + e.toString());
	}

	/**
	 * 环绕通知 环绕通知非常强大，可以决定目标方法是否执行，什么时候执行，执行时是否需要替换方法参数，执行完毕是否需要替换返回值。
	 * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
	 * @param proceedingJoinPoint
	 */
	@Around(value = "execution(* com.levi.controller.TestAopController.*(..))")
	public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
		log.info("开始环绕通知:>>>");
		String name = proceedingJoinPoint.getSignature().getName();
		log.info("开始环绕通知的目标方法名是:>>" + name);
		try {
			return proceedingJoinPoint.proceed();
		}
		catch (Throwable e) {
			e.printStackTrace();
		}
		finally {
			log.info("环绕通知结束！");
		}
		return null;
	}

}
